#include "maploader.h"

#include "pickup.h"
#include "tile.h"

CMap *CMapLoader::loadMap(string filePath, bool bDemo)
{

    /**
     * Loads the map from a binary file located at the file path passed in
     *
     * @param string filePath The path of the map to load.
     */

    srand(static_cast<unsigned int>(time(NULL)));

    CFileReader file;
    try
    {
        file.setFilePath(filePath);
    }
    catch (ifstream::failure)
    {
        // Cannot open file.
        stringstream sstream;
        sstream << "Cannot load map at path: " 
                << filePath
                << "\n\nIO Error occurred, could not open the map.\n\n";
        throw utils::ErrMapLoader(sstream.str());
    }
    
    // Read in the version of the map to test if the game can use it.
    short int nVersion(file.readShort());

    if (nVersion != Constants::CURRENT_MAP_VERSION)
    {
        // If the map version is not equal to the current map version being used
        // by our game then throw an error.
        stringstream sstream;
        sstream << "Cannot load map at path: " 
                << filePath
                << ".\n\nVersions do not match.\n\n"
                << "Current version required: "
                << Constants::CURRENT_MAP_VERSION
                << "\nMap version: "
                << nVersion;
        throw utils::ErrMapLoader(sstream.str());
    }

    // Get the name of the map by reading in how many characters are used for 
    // the name and then reading in that amount of characters.


    CMap *pMap(new CMap(bDemo));

    BYTE nMapNameLength(file.readChar());
    pMap->setMapName(file.readString(nMapNameLength));


    BYTE nSkyboxPath(file.readChar());
    pMap->setSkyBox(file.readString(nSkyboxPath));

    // Read the map dimensions.
    int nHeight(file.readShort());
    int nWidth(file.readShort());
    int nDepth(file.readShort());
    pMap->setSize(nHeight, nWidth, nDepth);
    //pMap->setSize(0, 0, 0);

    pMap->setMaxPlayerCount(file.readChar());

    if (pMap->getHeight() <=0){
        stringstream sstream;
        sstream << "The map needs to have at least a HEIGHT of size 1. It" 
                << " currently has a height of size "
                << pMap->getHeight()
                << ".\n\nPlease increase the maps height.\n\n";
        throw utils::ErrMapLoader(sstream.str());
    }

    if (pMap->getWidth() <=2)
    {
        stringstream sstream;
        sstream << "The map needs to have at least a WIDTH of size 3. It" 
                << " currently has a width of size "
                << pMap->getWidth()
                << ".\n\nPlease increase the maps width.\n\n";
        throw utils::ErrMapLoader(sstream.str()); 
    }

    if (pMap->getDepth() <= 2)
    {
        stringstream sstream;
        sstream << "The map needs to have at least a DEPTH of size 3. It" 
                << " currently has a depth of size "
                << pMap->getDepth()
                << ".\n\nPlease increase the maps depth.\n\n";
        throw utils::ErrMapLoader(sstream.str()); 
    }

    if (pMap->getHeight() * (pMap->getWidth() - 2) * (pMap->getDepth() - 2) < 
        pMap->getMaxPlayerCount())
    {
        stringstream sstream;
        sstream << "The map is not big enough to accommodate all players. " 
                << "It can currently hold "
                << (pMap->getHeight() * (pMap->getWidth() - 2) * 
                   (pMap->getDepth() -2))
                << " of players."
                << ".\n\nPlease increase the maps height, width or depth.\n\n";
        throw utils::ErrMapLoader(sstream.str()); 
    }

    /*if (pMap->getMaxPlayerCount() <= 0){
        stringstream sstream;
        sstream << "You need at least one player to play this map." ;
        throw utils::ErrMapLoader(sstream.str()); 
    }*/
    
    // Get the amount of different pickups on the map.
    pMap->setPickupCount(file.readShort());

    if (pMap->getPickupCount() < 0){
        stringstream sstream;
        sstream << "Invalid amount of different pickups."; 
        throw utils::ErrMapLoader(sstream.str()); 
    }

    for (int i(0); i < pMap->getPickupCount(); ++i){
        // For each pick up read what pick up it is and how many there are on 
        // the map.
        short int nPickUpID(file.readShort());
        short int n_MaxamountOfPickup(file.readShort());


        // Then insert it into the array of pickups
        for (int j(0); j < n_MaxamountOfPickup; ++j){

            pMap->addPickup(utils::findPickup(nPickUpID, 0.0f, 0.0f, 0.0f));
        }

        pMap->setPickupIdCount(nPickUpID, n_MaxamountOfPickup);
       
    }

    // Read the amount of destructible tiles and solid tiles
    //pMap->setDestructibleTileCount(file.readLong());

    int nAmountOfDestructibleTypes(file.readShort());

    for (int nCurrentDestructibleType(0); 
         nCurrentDestructibleType < nAmountOfDestructibleTypes; 
         ++nCurrentDestructibleType)
    {
        // For every different type of destructible tile
        int nDestructibleTileID(file.readShort());
        int nAmountOfType(file.readShort());

        pMap->addDestructibleTileCount(nDestructibleTileID, nAmountOfType);
    }

    pMap->setTileCount(file.readLong());
        
    /*if (pMap->getTileCount() <= 0 || 
        pMap->getTileCount() > (pMap->getHeight() * (pMap->getWidth() - 2) * 
        (pMap->getDepth()) -2) - pMap->getMaxPlayerCount())
    {
        stringstream sstream;
        sstream << "There is not enough space to render all solid tiles."
                << " You can have a maximum of "
                << ((pMap->getHeight() * (pMap->getWidth() - 2) * 
                    (pMap->getDepth() -2) - pMap->getMaxPlayerCount()))
                << ".";
        throw ErrMapLoader(sstream.str()); 
    }

    if (pMap->getDestructibleTileCount() <=0 || 
        pMap->getDestructibleTileCount() > (pMap->getHeight() * (pMap->getWidth() - 2) * 
        (pMap->getDepth()) -2) - pMap->getMaxPlayerCount() - 
        pMap->getTileCount())
    {
        stringstream sstream;
        sstream << "There is not enough space to render all destructible tiles."
                << " You can have a maximum of "
                << ((pMap->getHeight() * (pMap->getWidth() - 2) * 
                   (pMap->getDepth()) -2) - pMap->getMaxPlayerCount() - 
                   pMap->getTileCount())
                << ".";
        throw ErrMapLoader(sstream.str()); 
    }*/

    // Add the tile into the Map by adding them floor by floor.
    for (int j(0); j < nHeight; ++j){
        FLOOR Floor;
        vector<vector<short int>> m_arrTileIDs;
        for (int i(0); i < nWidth; ++i){
            // Create the rows tile by tile.
            ROW Row;
            vector<short int> arrTileRow;
            for (int k(0); k < nDepth; ++k){
                short int nTileID (file.readShort());
                arrTileRow.push_back(nTileID);
                CTile *tile(utils::findTile(nTileID, j, i, k));

                if (k >= 1 && Row[k-1]->getID() != ENT_TILE_FREE)
                {
                    tile->getDisplayable()->setSubsetNonDrawable(
                        TILE_FACE_FRONT);
                    if (tile->getID() != ENT_TILE_FREE)
                    {
                        Row[k-1]->getDisplayable()->setSubsetNonDrawable(
                            TILE_FACE_BACK);
                    }
                }
                if (i >= 1 && Floor[i-1][k]->getID() != ENT_TILE_FREE)
                {
                    tile->getDisplayable()->setSubsetNonDrawable(
                        TILE_FACE_LEFT);

                    if (tile->getID() != ENT_TILE_FREE)
                    {
                        Floor[i-1][k]->getDisplayable()->setSubsetNonDrawable(
                            TILE_FACE_RIGHT);
                    }
                }

                if (j >= 1 && pMap->getTile(j-1, i, k)->getID() != 
                    ENT_TILE_FREE)
                {
                    tile->getDisplayable()->setSubsetNonDrawable(
                        TILE_FACE_BOTTOM);
                    if (tile->getID() != ENT_TILE_FREE)
                    {
                        pMap->getTile(j-1, i, k)->getDisplayable()->\
                            setSubsetNonDrawable(TILE_FACE_TOP);
                    }
                }

                tile->onCreate(pMap);

                Row.push_back(tile);
            }
            // Add the row to the floor.
            m_arrTileIDs.push_back(arrTileRow);
            Floor.push_back(Row);
        }
        // Add the floor to the map.
        pMap->addFloorIds(m_arrTileIDs);
        pMap->addFloor(Floor);
    }

    // Read in the amount of entities in the map.
    pMap->setEntityCount(file.readShort());
    
    
    for (int i(0); i < pMap->getEntityCount(); ++i)
    {
        // For all the entities read where they are and what they are.

        short int nEntityID(file.readShort());
        float fX(file.readFloat());
        float fY(file.readFloat());
        float fZ(file.readFloat());
        bool bVisible(file.readBool());
        short int amountOfParams(file.readShort());
        vector<PARAM> params(utils::getEntityParams(file, amountOfParams));

        CEntity *pEntity(findEntity(nEntityID, fX, fY, fZ, bVisible, 
                                          params));
        CEntityContainer::getInstance()->addEntity(pEntity);
        pEntity->setPermanent(true);
        pMap->addEntity(pEntity);
        pEntity->onCreate(pMap);
        if (pEntity != nullptr)
        {
            pEntity->setVisible(false);
        }
    }

    //pMap->setEntityCount(0);

    /* get an array of positions of the spawn points */ 
    
    int spawnPoints[16][3];
    int ammountOfPlayers(0);
    for(int i(0); i < pMap->getEntityCount(); i++)
    {
        if(pMap->getEntityFromID(i)->getID() == ENT_PLAYER_SPAWN){
            spawnPoints[ammountOfPlayers][0]= static_cast<unsigned int>(
                pMap->getEntityFromID(i)->getY()) / 64;
            spawnPoints[ammountOfPlayers][1]= static_cast<unsigned int>(
                pMap->getEntityFromID(i)->getX()) / 64;
            spawnPoints[ammountOfPlayers++][2]= static_cast<unsigned int>(
                pMap->getEntityFromID(i)->getZ()) / 64;
        }
    }

    int freeSpaces[1024][3];
    int amountOfFreeTiles(0);

    for (int j(0); j < pMap->getHeight(); ++j)
    {

        for (int i(0); i < pMap->getWidth(); ++i)
        {

            for (int k(0); k < pMap->getDepth(); ++k)
            {

                CTile *pTile(pMap->getTile(j, i, k));
                if(pTile->getID() == ENT_TILE_FREE)
                {

                    /* Check if this position is in that array if it isn't add it if it is ignore it */

                    bool bValidTile(true);

                    for(int player(0); player < ammountOfPlayers; ++player)
                    {
                        if(pTile->getHeight() == spawnPoints[player][0])
                        {
                            if ((abs(pTile->getWidth() - spawnPoints[player][1]) <= 2 &&
                                 pTile->getDepth() == spawnPoints[player][2]) ||
                                (abs(pTile->getDepth() - spawnPoints[player][2]) <= 2) &&
                                 pTile->getWidth() == spawnPoints[player][1])
                            {
                                bValidTile = false;
                                break;
                            }
                        }
                    }

                    if (bValidTile)
                    {
                        freeSpaces[amountOfFreeTiles][0] = j;
                        freeSpaces[amountOfFreeTiles][1] = i;
                        freeSpaces[amountOfFreeTiles++][2] = k;
                    }
                }
            }
        }
    }

    int nPickupIndex(0);
    vector<short> arrTiles(pMap->getDestructibleTileIds());
    for (vector<short>::const_iterator pType(arrTiles.begin()); 
         pType != arrTiles.end(); ++pType)
    {
        for (int i(0); i < pMap->getDestructibleIdCount(*pType); ++i)
        {

            if (amountOfFreeTiles <= 0)
            {
                continue;
            }
            int index(0);
            if (amountOfFreeTiles > 1)
            {
                index = rand() % (amountOfFreeTiles - 1);
            }                

            CTile* pTile = utils::findTile(*pType, 
                freeSpaces[index][0], freeSpaces[index][1], 
                freeSpaces[index][2]);

            pMap->setTile(freeSpaces[index][0],
                freeSpaces[index][1], freeSpaces[index][2], pTile);

            if (! bDemo)
            {
                CPickup *pPickup(pMap->getPickupFromIndex(nPickupIndex++));

                if (pPickup != nullptr)
                {
                    pPickup->setPosition(pTile->getX(), pTile->getY(), 
                        pTile->getZ());
                    pTile->setPickup(pPickup);
                    pPickup->setPermanent(false);
                }
            }

            if (amountOfFreeTiles > 0)
            {
                freeSpaces[index][0] = freeSpaces[--amountOfFreeTiles][0];
                freeSpaces[index][1] = freeSpaces[amountOfFreeTiles][1];
                freeSpaces[index][2] = freeSpaces[amountOfFreeTiles][2];
            }
        }
    }

    CMeshContainer *pMeshes(CMeshContainer::getInstance());
    CTextureContainer *pTextures(CTextureContainer::getInstance());

    CTexture *pTexture(pTextures->getTexture("explosion1"));
    if (pTexture == nullptr && !bDemo)
    {
        pTextures->addTexture("explosion1", 
            "textures/tiles/explosions/explosion1.png", 
            CDeviceManager::getInstance()->getDevice());

        pTexture = pTextures->addTexture("explosion2", "textures/tiles/explosions/explosion2.png",
            CDeviceManager::getInstance()->getDevice());

        pTexture = pTextures->addTexture("explosion3", "textures/tiles/explosions/explosion3.png",
            CDeviceManager::getInstance()->getDevice());

        if (pMeshes->getMesh("weapons/bomb") == nullptr)
        {
            CMesh *pBomb(pMeshes->addMesh("weapons/bomb"));
                pBomb->loadMesh(CDeviceManager::getInstance()->getDevice(), 
                "models/weapons/bomb.x");
        }
    }


    pMap->resetCameraToDefault();

    return pMap;
}

void CMapLoader::reloadMap(CMap *pMap)
{
    pMap->reset();

    for (int j(0); j < pMap->getHeight(); ++j){
        FLOOR Floor;
        for (int i(0); i < pMap->getWidth(); ++i){
            // Create the rows tile by tile.
            ROW Row;
            vector<short int> arrTileRow;
            for (int k(0); k < pMap->getDepth(); ++k){
                
                CTile *tile(utils::findTile(pMap->getTileID(j, i, k), j, i, k));

                if (k >= 1 && Row[k-1]->getID() != ENT_TILE_FREE)
                {
                    tile->getDisplayable()->setSubsetNonDrawable(
                        TILE_FACE_FRONT);
                    if (tile->getID() != ENT_TILE_FREE)
                    {
                        Row[k-1]->getDisplayable()->setSubsetNonDrawable(
                            TILE_FACE_BACK);
                    }
                }
                if (i >= 1 && Floor[i-1][k]->getID() != ENT_TILE_FREE)
                {
                    tile->getDisplayable()->setSubsetNonDrawable(
                        TILE_FACE_LEFT);

                    if (tile->getID() != ENT_TILE_FREE)
                    {
                        Floor[i-1][k]->getDisplayable()->setSubsetNonDrawable(
                            TILE_FACE_RIGHT);
                    }
                }

                if (j >= 1 && pMap->getTile(j-1, i, k)->getID() != 
                    ENT_TILE_FREE)
                {
                    tile->getDisplayable()->setSubsetNonDrawable(
                        TILE_FACE_BOTTOM);
                    if (tile->getID() != ENT_TILE_FREE)
                    {
                        pMap->getTile(j-1, i, k)->getDisplayable()->\
                            setSubsetNonDrawable(TILE_FACE_TOP);
                    }
                }

                tile->onCreate(pMap);

                Row.push_back(tile);
            }
            // Add the row to the floor.
            Floor.push_back(Row);
        }
        // Add the floor to the map.
        pMap->addFloor(Floor);
    }

    CEntityContainer *pEntities(CEntityContainer::getInstance());
    pEntities->reset();

    vector<short int> arrPickupIds(pMap->getPickupIds());
    for (vector<short int>::iterator pId(arrPickupIds.begin()); 
         pId != arrPickupIds.end(); ++pId)
    {
        for (int nPickupCount(0); nPickupCount < pMap->getPickupIdCount(*pId);
             ++nPickupCount)
        {
            pMap->addPickup(utils::findPickup(*pId, 0.0f, 0.0f, 0.0f));
        }
    }

    int spawnPoints[16][3];
    int ammountOfPlayers(0);
    for(int i(0); i < pMap->getEntityCount(); i++)
    {
        if(pMap->getEntityFromID(i)->getID() == ENT_PLAYER_SPAWN){
            spawnPoints[ammountOfPlayers][0]= static_cast<unsigned int>(
                pMap->getEntityFromID(i)->getY()) / 64;
            spawnPoints[ammountOfPlayers][1]= static_cast<unsigned int>(
                pMap->getEntityFromID(i)->getX()) / 64;
            spawnPoints[ammountOfPlayers++][2]= static_cast<unsigned int>(
                pMap->getEntityFromID(i)->getZ()) / 64;
        }
    }

    int freeSpaces[1024][3];
    int amountOfFreeTiles(0);

    for (int j(0); j < pMap->getHeight(); ++j)
    {

        for (int i(0); i < pMap->getWidth(); ++i)
        {

            for (int k(0); k < pMap->getDepth(); ++k)
            {

                CTile *pTile(pMap->getTile(j, i, k));
                if(pTile->getID() == ENT_TILE_FREE)
                {

                    /* Check if this position is in that array if it isnt add it if it is ignore it */

                    bool bValidTile(true);

                    for(int player(0); player < ammountOfPlayers; ++player)
                    {
                        if(pTile->getHeight() == spawnPoints[player][0])
                        {
                            if ((abs(pTile->getWidth() - spawnPoints[player][1]) <= 2 &&
                                 pTile->getDepth() == spawnPoints[player][2]) ||
                                (abs(pTile->getDepth() - spawnPoints[player][2]) <= 2) &&
                                 pTile->getWidth() == spawnPoints[player][1])
                            {
                                bValidTile = false;
                                break;
                            }
                        }
                    }

                    if (bValidTile)
                    {
                        freeSpaces[amountOfFreeTiles][0] = j;
                        freeSpaces[amountOfFreeTiles][1] = i;
                        freeSpaces[amountOfFreeTiles++][2] = k;
                    }
                }
            }
        }
    }

    /*for (int i(0); i < pMap->getDestructibleTileCount(); i++)
    {
        int index(0);
        if (amountOfFreeTiles > 1)
        {
            index = rand() % (amountOfFreeTiles - 1);
        }
        CPickup *pPickup(pMap->getPickupFromIndex(i));
        
        CTile* pTile = utils::findTile(ENT_TILE_DESTRUCTIBLE, 
        freeSpaces[index][0], freeSpaces[index][1], freeSpaces[index][2]);
        pMap->setTile(freeSpaces[index][0], freeSpaces[index][1], 
        freeSpaces[index][2], pTile);*/
    int nPickupIndex(0);
    vector<short> arrTiles(pMap->getDestructibleTileIds());
    for (vector<short>::const_iterator pType(arrTiles.begin()); 
        pType != arrTiles.end(); ++pType)
    {
        for (int i(0); i < pMap->getDestructibleIdCount(*pType); ++i)
        {

            if (amountOfFreeTiles <= 0)
            {
                continue;
            }
            int index(0);
            if (amountOfFreeTiles > 1)
            {
                index = rand() % (amountOfFreeTiles - 1);
            }

            CPickup *pPickup(pMap->getPickupFromIndex(nPickupIndex++));

            CTile* pTile = utils::findTile(*pType, 
                freeSpaces[index][0], freeSpaces[index][1], 
                freeSpaces[index][2]);

            pMap->setTile(freeSpaces[index][0],
                freeSpaces[index][1], freeSpaces[index][2], pTile);

            if (pPickup != nullptr)
            {
                pPickup->setPosition(pTile->getX(), pTile->getY(), pTile->getZ());
                pTile->setPickup(pPickup);
            }

            if (amountOfFreeTiles > 0)
            {
                freeSpaces[index][0] = freeSpaces[--amountOfFreeTiles][0];
                freeSpaces[index][1] = freeSpaces[amountOfFreeTiles][1];
                freeSpaces[index][2] = freeSpaces[amountOfFreeTiles][2];
            }
        }
    }
}